from typing import List
import matplotlib

matplotlib.use('Agg')  # 设置后端
import matplotlib.pyplot as plt
from Env.BaseStations.train import Train
from Env.BaseStations.base_station import Base_Station
from Env.nodes import User_Node
from Env.BaseStations.uav import UAV
import datetime
import concurrent.futures
from Env.channel import *


class Environment:
    def __init__(self, id, n_users, n_uavs, n_BSs, n_Trains, flight_time):
        self.id = id
        self.time_res = 1
        self.multi_agent = n_uavs > 1
        self.flight_time = flight_time
        self.a = 9.61  # 常数a，用于计算无线信号的传播损耗等
        self.b = 0.16  # 常数b，用于计算无线信号的传播损耗等
        self.mu_LOS = 1  # 视距传播的信道增益
        self.mu_NLOS = 20  # 非视距传播的信道增益
        self.SIGMA_2 = 1e-11  # 噪声功率
        self.coverage_radius = 70  # 无人机的覆盖范围半径
        """
        Space Borders
        """
        self.MAX_X = 200
        self.MIN_X = 0
        self.MAX_Y = 40
        self.MIN_Y = 0
        self.borders = [np.array([self.MIN_X, self.MIN_Y]), np.array([self.MAX_X, self.MAX_Y])]
        self.time = 0
        self.slot_length = 2
        """
        Users definition
        """
        self.num_users = n_users
        self.users: List[User_Node] = self.num_users * [None]
        self.seed = 1
        if self.seed is not None:
            np.random.seed(self.seed)
            # 生成用户 ID
        users_id = ['user_' + str(i) for i in range(self.num_users)]

        # 使用 numpy 随机生成用户的位置
        # users_initial_location = [np.array([np.random.randint(x_min, x_max + 1),np.random.randint(y_min, y_max + 1),0], dtype=int)for _ in range(self.num_users)]
        # 固定传感器的位置
        users_initial_location = [
            np.array([10, 10, 0], dtype=int),
            np.array([30, 25, 0], dtype=int),
            np.array([60, 15, 0], dtype=int),
            np.array([90, 30, 0], dtype=int),
            np.array([110, 15, 0], dtype=int),
            np.array([130, 20, 0], dtype=int),
            np.array([150, 20, 0], dtype=int),
            np.array([165, 30, 0], dtype=int),
            np.array([180, 5, 0], dtype=int),
            np.array([190, 25, 0], dtype=int)
        ]
        sampling_intervals = [
            np.random.randint(5, 10) for _ in range(self.num_users)
        ]
        """
        Base Stations definition
        """
        self.num_BSs = n_BSs
        self.BSs: List[Base_Station] = self.num_BSs * [None]
        BSs_id = ['BS_' + str(i) for i in range(self.num_BSs)]
        BSs_initial_location = [np.array([100, 20, 20], dtype=np.float64)]

        """
        Train definition
        """
        self.num_Trains = n_Trains
        self.Trains: List[Train] = self.num_Trains * [None]
        trains_id = ['Train_' + str(i) for i in range(self.num_Trains)]
        trains_initial_location = [np.array([170, 30, 0], dtype=np.float64)]
        """
        UAVs definition
        """
        self.uav_obs_range = 10000

        self.num_uavs = n_uavs
        self.uavs: List[UAV] = self.num_uavs * [None]
        uavs_id = ['uav_' + str(i) for i in range(self.num_uavs)]
        uavs_initial_location = [
            np.array([50., 10., 50.], dtype=np.float64),  # UAV 1 初始位置
            np.array([150., 10., 50.], dtype=np.float64)  # UAV 2 初始位置
        ]  # 批量建立用户节点、基站、无人机、列车的实例对象
        with concurrent.futures.ThreadPoolExecutor() as executor:
            self.users = list(executor.map(self.create_user, users_id, users_initial_location, sampling_intervals))
            self.BSs = list(executor.map(self.create_base_station, BSs_id, BSs_initial_location))
            self.uavs = list(executor.map(self.create_uav, uavs_id, uavs_initial_location))
            self.trains = list(executor.map(self.create_train, trains_id, trains_initial_location))

            # 设置 other_users
            for user in self.users:
                other_users = [other_user for other_user in self.users if other_user.id != user.id]
                user.set_other_users(other_users)

            # 设置 other_uavs
            for uav in self.uavs:
                other_uavs = [other_uav for other_uav in self.uavs if other_uav.id != uav.id]
                uav.set_other_uavs(other_uavs)
                uav.coverage_penalty = [0] * self.num_users

        # 初始化性能指标
        self.avg_aoi_dc = 0.0
        self.avg_aoi_ue = 0.0
        self.total_user_energy = 0.0
        self.total_uav_energy = 0.0
        '''
        初始化状态和观测变量
        '''
        # 初始化公共观测
        self.AoI_DC_delta_list = np.zeros(self.num_users)  # 作为奖励
        self.observation_p = np.zeros((3, self.num_users))  # 每个SN有3个观测值: A_k^SN, A_k^DC, φ_k——AoI和队列信息
        self.AoI_DC_list = np.zeros(self.num_users)
        self.AoI_UE_list = np.zeros(self.num_users)
        self.user_cache_info = np.zeros(self.num_users)
        # 初始化每个无人机的私有观测
        self.uav_obs = np.zeros((self.num_uavs, self.num_users + self.num_uavs + 3))  # 每个观测有1个值: 无人机坐标和相对位置
        # action维度,飞行角度，飞行速度，向每个SN的任务卸载比例
        self.action_dim = np.zeros((self.num_uavs, 2 + self.num_users))
        # 初始化每个无人机(agent)的actor输入（公共观测+私有观测）
        self.obs_space_dims = (self.num_users + self.num_uavs + 3) + (self.num_users * 3)  # actor输入维度
        self.obs = np.zeros((self.num_uavs, self.obs_space_dims))  # 观测空间 所有UAV的actor网络输入的集合
        # 初始化reward
        self.reward = np.zeros(self.num_uavs)

    def create_user(self, User_id, location, sampling_interval):
        return User_Node(id=User_id, initial_location=location, sampling_interval=sampling_interval)

    def create_base_station(self, BS_id, location):
        return Base_Station(id=BS_id, initial_location=location)

    def create_uav(self, uav_id, location):
        return UAV(id=uav_id, initial_location=location, env_borders=self.borders)

    def create_train(self, train_id, location):
        return Train(id=train_id, initial_location=location)

    @staticmethod
    def generate_random_location() -> np.ndarray:
        # 随机生成位置，可以根据需要修改范围
        x = np.random.uniform(-1000, 1000)
        y = np.random.uniform(-1000, 1000)
        z = np.random.uniform(0, 100)
        return np.array([x, y, z], dtype=np.float64)

    def reward_function(self, uav_idx):
        reward_sn_ec = 0  # SN能耗
        reward_AoI_DC = np.mean(self.AoI_DC_list)  # SN AoI
        # print(self.AoI_DC_list)
        for user in self.users:
            reward_sn_ec += user.upload_ec
            # print(user.upload_ec)
        if self.uavs[uav_idx].collision_occurred():
            collision_penalty = 1
        else:
            collision_penalty = 0
        if self.uavs[uav_idx].out_of_border():
            out_of_border_penalty = 1
        else:
            out_of_border_penalty = 0
        # 添加惩罚列表的总和
        reward_uav_trans_ec = self.uavs[uav_idx].trans_ec  # 无人机传输能耗
        reward_uav_comp_ec = self.uavs[uav_idx].comp_ec  # 无人机计算能耗
        reward_uav_flight_ec = self.uavs[uav_idx].flight_ec  # 无人机飞行能耗
        reward_uav_ec = reward_uav_trans_ec + reward_uav_comp_ec + reward_uav_flight_ec  # UAV总能耗

        reward_sn_ec = reward_sn_ec * 100  # SN总能耗
        reward_uav_ec = reward_uav_ec * 0.005  # UAV总能耗
        reward_AoI_DC = reward_AoI_DC * 1  # AoI
        collision_penalty = collision_penalty * 1  # UAV碰撞
        out_of_border_penalty = out_of_border_penalty * 1  # UAV出界

        reward = - reward_AoI_DC - reward_uav_ec - reward_sn_ec - collision_penalty - out_of_border_penalty
        # 输出每个部分的奖励值
        #print(f"AoI: {reward_AoI_DC},UAV总能耗: {reward_uav_ec}, SN总能耗: {reward_sn_ec}, UAV碰撞: {collision_penalty}, UAV出界: {out_of_border_penalty} ")
        return reward  # 矩阵乘法

    def assign_offload_partitions(self, offload_partition):
        offload_partition = np.array(offload_partition)
        results = []
        # 将 offload_partition 分配给每个用户
        for user_idx in range(self.num_users):
            user = self.users[user_idx]
            user.offload_partition = offload_partition[:, user_idx]  # 将对应的 UAV 卸载比例分配给用户

        # 遍历每个用户，找到离其最近的UAV
        for user_idx in range(self.num_users):
            user = self.users[user_idx]
            user.connected_uav = None  # 初始化与用户连接的 UAV
            closest_partition_value = 0  # 用户的卸载比例初始化为 0
            min_distance = float('inf')  # 初始化最小距离为无穷大

            # 遍历 UAV，检查 offload_partition 和覆盖范围
            for uav_idx in range(self.num_uavs):
                partition_value = user.offload_partition[uav_idx]  # 获取对应 UAV 的卸载比例

                # 跳过如果当前 UAV 的卸载比例为0
                if partition_value == 0:
                    continue

                # 计算用户与 UAV 的距离
                distance = np.linalg.norm(user.location - self.uavs[uav_idx].location)

                # 判断 UAV 是否在覆盖范围内
                if distance > self.coverage_radius:
                    user.offload_partition[uav_idx] = 0  # 超出覆盖范围则设置为0
                    # self.uavs[uav_idx].coverage_penalty[user_idx] = 0.05
                    continue  # 继续检查其他 UAV

                # 如果在覆盖范围内，检查是否是最近的 UAV
                if distance < min_distance:
                    min_distance = distance
                    user.connected_uav = self.uavs[uav_idx]  # 更新连接的 UAV
                    closest_partition_value = partition_value  # 记录最近 UAV 的卸载比例

            # 如果没有找到合适的 UAV，将连接状态和卸载比例重置
            if user.connected_uav is None:
                user.connected_uav = None  # 所有 UAV 的 offload_partition 最终均为0，用户不进行数据上传
                closest_partition_value = 0

            # 将最近 UAV 的卸载比例赋值给用户
            user.final_offload_partition = closest_partition_value  # 保存最近 UAV 的卸载比例

            # 将连接的 UAV ID 和对应的卸载比例添加到结果列表
            if user.connected_uav is not None:
                results.append((user_idx, user.connected_uav.id, user.final_offload_partition))
            else:
                results.append((user_idx, None, 0))  # 没有连接 UAV 状态
        # print(results)

    def offloading_and_computing(self):
        # 初始化该时隙所有UAV的计算总能耗
        for uav_idx in range(self.num_uavs):
            self.uavs[uav_idx].comp_ec = 0
        for user_idx, user in enumerate(self.users):
            user.upload_state = 0
            # 初始化计算时间
            oc_time = 0
            # 获取该用户对应的卸载比例
            partition_value = user.final_offload_partition
            # 根据阈值调整卸载比例
            if 0.5 <= partition_value <= 1:
                partition_value = 1  # 越接近1取1
            elif partition_value < 0.5:
                partition_value = 0  # 越接近0取0
            # 不进行上传，AoI递增
            if partition_value == 0 or user.cache_state == 0:
                old_AoI_DC = user.AoI_DC
                user.AoI_DC += self.slot_length
                user.upload_ec = 0
                user.AoI_DC_delta = old_AoI_DC - user.AoI_DC
            else:
                # 连接的uav
                user.upload_state = 1
                c_uav = user.connected_uav
                # 计算上传速率
                upload_rate = data_rate(user, c_uav, self.a, self.b, self.mu_LOS, self.mu_NLOS, self.SIGMA_2)
                # print(upload_rate)
                upload_time = user.data_size / upload_rate
                user.upload_ec = upload_time * user.t_power
                # 如果卸载比例为1，则完全在无人机本地计算
                if partition_value == 1:
                    comp_time_uav = user.data_size * user.comp_density / c_uav.comp_resource
                    c_uav.comp_ec += c_uav.compute_energy(user.comp_density * user.data_size)  # 无人机对该SN的计算能耗
                    # AoI 计算：上传时间 + 无人机本地计算时间
                    oc_time = upload_time + comp_time_uav
                # 如果卸载比例介于 1 和 2 之间，部分卸载到基站
                elif 1 < partition_value <= 2:
                    offload_ratio_bs = partition_value - 1  # 基站的卸载部分
                    nearest_bs = self.BSs[0]  # 假设总是使用一个固定基站
                    offload_rate = data_rate(c_uav, nearest_bs, self.a, self.b, self.mu_LOS, self.mu_NLOS,
                                             self.SIGMA_2)
                    offload_time = user.data_size * offload_ratio_bs / offload_rate   # 卸载到基站的时间
                    comp_time_bs = user.data_size * offload_ratio_bs * user.comp_density / nearest_bs.comp_resource  # 基站计算时间
                    comp_time_uav = user.data_size * (1 - offload_ratio_bs) * user.comp_density / c_uav.comp_resource  # 无人机计算时间
                    c_uav.comp_ec += c_uav.compute_energy(user.comp_density * user.data_size * (1 - offload_ratio_bs))  # 更新无人机计算能耗
                    # AoI 计算：上传时间 + 卸载时间 + 最大的计算时间（本地和基站）
                    oc_time = upload_time + offload_time + max(comp_time_uav, comp_time_bs)
                # 如果卸载比例介于 2 和 3 之间，部分卸载到列车
                elif 2 < partition_value <= 3:
                    offload_ratio_train = partition_value - 2  # 列车的卸载部分
                    nearest_train = self.trains[0]  # 假设总是使用一个固定列车
                    offload_rate = UtoT_data_rate(c_uav, nearest_train, self.a, self.b, self.mu_LOS, self.mu_NLOS,
                                                  self.SIGMA_2, 1)
                    offload_time = user.data_size * offload_ratio_train / offload_rate
                    comp_time_train = user.data_size * offload_ratio_train * user.comp_density / nearest_train.comp_resource
                    comp_time_uav = user.data_size * (1 - offload_ratio_train) * user.comp_density / c_uav.comp_resource
                    c_uav.comp_ec += c_uav.compute_energy(
                        user.comp_density * user.data_size * (1 - offload_ratio_train))
                    # AoI 计算：上传时间 + 卸载时间 + 最大的计算时间（本地和列车）
                    oc_time = upload_time + offload_time + max(comp_time_uav, comp_time_train)
                # 更新 AoI（信息陈旧度）
                old_AoI_DC = user.AoI_DC
                user.AoI_DC = user.AoI_UE + oc_time
                user.AoI_DC_delta = old_AoI_DC - user.AoI_DC
                # print(1)
        # 直接输出每个用户的 AoI_DC 值
        # print([user.AoI_DC for user in self.users])

    def move_uav(self, uav, action_angle, action_velocity, delta_time):
        return uav.move(action_angle, action_velocity, delta_time)

    def get_relative_position(self, entity1, entity2):
        return np.linalg.norm(entity1.location - entity2.location)

    def update_performance_metrics(self):
        """计算并更新性能指标的平均值"""
        total_aoi_dc = sum(user.AoI_DC for user in self.users)
        total_aoi_ue = sum(user.AoI_UE for user in self.users)

        # 计算每个用户的上传能耗总和
        self.total_user_energy = sum(user.upload_ec for user in self.users)
        # 计算无人机总能耗
        self.total_uav_energy = sum(
            uav.trans_ec + uav.comp_ec + uav.flight_ec for uav in self.uavs
        )

        self.avg_aoi_dc = total_aoi_dc / self.num_users
        self.avg_aoi_ue = total_aoi_ue / self.num_users

    def calculate_distances(self, uav):
        """计算一个无人机与所有其他对象的距离"""
        user_distances = [self.get_relative_position(uav, user) for user in self.users]
        uav_distances = [self.get_relative_position(uav, other_uav) for other_uav in self.uavs if other_uav != uav]
        bs_distances = [self.get_relative_position(uav, bs) for bs in self.BSs]
        train_distances = [self.get_relative_position(uav, train) for train in self.trains]
        return np.concatenate([user_distances, uav_distances, bs_distances, train_distances]).reshape(-1, 1)

    def step(self, actions):
        """执行一步动作。param actions: 包含每个智能体的动作的列表，每个动作包括方向、速度和卸载比例 """
        directions, speeds, offload_ratios = [], [], []
        for action in actions:
            # 方向（theta_u）：范围 [-1, 1] 不需要转换
            direction = action[0] * 2 * np.pi
            # 速度（l_u）：从 [-1, 1] 转换到 [-3, 3]
            acceleration = action[1] * 5
            # 卸载比率（O_mk_x）：从 [-1, 1] 转换到 [0, 3]
            offload_ratio = action[2:] * 3
            directions.append(direction)
            speeds.append(acceleration)
            offload_ratios.append(offload_ratio)
        # print("航向 :", directions, "速度 :", speeds)
        self.time += 1
        for train in self.trains:
            train.move_train()
        # UAV先移动位置
        with concurrent.futures.ThreadPoolExecutor() as executor:
            executor.map(self.move_uav, self.uavs, directions, speeds, self.num_uavs * [self.slot_length - 1])
        # UAV悬停进行数据收集，再进行卸载与计算更新
        self.assign_offload_partitions(offload_ratios)
        self.offloading_and_computing()
        done = [self.time >= self.flight_time] * self.num_uavs

        # 更新公共观测
        for m, user in enumerate(self.users):
            user.cache_update(self.time)
            self.AoI_DC_delta_list[m] = user.AoI_DC_delta
            self.AoI_DC_list[m] = user.AoI_DC  # 更新AoI_DC
            self.AoI_UE_list[m] = user.AoI_UE  # 更新AoI_UE
            self.user_cache_info[m] = user.cache_state  # 更新user缓存
        # print(f"AoI_DC: {', '.join(map(str, self.AoI_DC_list))}")        #print(self.AoI_DC_list)
        # 更新公共观测，直接更新现有数组的内容
        self.observation_p[0, :] = self.AoI_DC_list
        self.observation_p[1, :] = self.AoI_UE_list
        self.observation_p[2, :] = self.user_cache_info

        with concurrent.futures.ThreadPoolExecutor() as executor:  # 创建线程池，多线程执行
            # 提交计算任务
            future_to_uav = {executor.submit(self.calculate_distances, uav): m for m, uav in enumerate(self.uavs)}
            # 处理已完成的任务
            for future in concurrent.futures.as_completed(future_to_uav):
                # 获取当前任务的uav索引
                m = future_to_uav[future]
                distances = future.result()
                # 确保 distances 是一维数组，展平它
                distances_flattened = distances.flatten()
                uav_location = self.uavs[m].location[:2]  # 假设 position 属性包含坐标，例如 [x, y]
                uav_location_flattened = np.array(uav_location).flatten()
                self.uav_obs[m] = np.hstack((uav_location_flattened, distances_flattened))  # 更新无人机的观测信息
                # 确保 observation_p 和 distances 可以拼接
                combined_observation = np.hstack(
                    (self.observation_p.flatten(), self.uav_obs[m]))
                # 将拼接后的结果赋值到 observation_space
                self.obs[m] = combined_observation
                self.reward[m] = self.reward_function(m)
        # print(self.reward)
        return [self.obs, self.reward, done]

    def reset_bs(self, bs_idx):
        self.BSs[bs_idx].reset()

    def reset_uav(self, uav_idx):
        self.uavs[uav_idx].reset()

    def reset_user(self, user_idx):
        self.users[user_idx].reset()

    def reset(self):
        self.time = 0

        for uav in self.uavs:
            uav.reset()

        for bs in self.BSs:
            bs.reset()

        for user in self.users:
            user.reset()

        for train in self.trains:
            train.reset()
        # log.info('All objects have been reset')

        obs = []
        for uav_idx in range(self.num_uavs):
            obs.append(self.observe(uav_idx))
        obs = np.array(obs, dtype=np.float32)
        return obs

    def observe(self, uav_idx):
        obs_uav = self.calculate_distances(self.uavs[uav_idx])
        uav_location = self.uavs[uav_idx].location[:2]
        uav_location_flattened = np.array(uav_location).flatten()
        self.uav_obs[uav_idx] = np.hstack((uav_location_flattened, obs_uav.flatten()))
        # 更新公共观测
        for m, user in enumerate(self.users):
            self.AoI_DC_list[m] = user.AoI_DC  # 更新AoI_DC
            self.AoI_UE_list[m] = user.AoI_UE  # 更新AoI_UE
            self.user_cache_info[m] = user.cache_state  # 更新user缓存
        # 初始化公共观测，直接更新现有数组的内容
        self.observation_p[0, :] = self.AoI_DC_list
        self.observation_p[1, :] = self.AoI_UE_list
        # 确保 observation_p 和 distances 可以拼接
        # 这两行代码的目的是将公共观测（self.observation_p）和私有观测（self.uav_obs[uav_idx]）拼接起来
        self.obs[uav_idx] = np.hstack((self.observation_p.flatten(), self.uav_obs[uav_idx]))
        return self.obs[uav_idx].flatten()

    def get_flattened_state(self):
        # 展平公共观测
        flattened_observation_p = self.observation_p.flatten()
        # 展平每个无人机的私有观测
        flattened_uav_observations = [self.uav_obs[m].flatten() for m in range(self.num_uavs)]
        # 整合所有观测
        state = np.hstack([flattened_observation_p] + flattened_uav_observations)
        return state

    def render(self, iter, save_dir):
        # 清空当前图形
        plt.clf()

        # 绘制用户
        for user in self.users:
            pt_x = user.location[0]
            pt_y = user.location[1]
            plt.scatter(pt_x, pt_y, c='red', marker='o', linewidths=0.05, alpha=1)  # 所有用户统一为红色
            plt.text(pt_x, pt_y - 3, f'{user.id}',  # 只显示用户ID
                     horizontalalignment='center', verticalalignment='center',
                     fontsize=6, color='red')  # 保持文本为红色

        # 绘制基站
        for bs in self.BSs:
            pt_x = bs.location[0]
            pt_y = bs.location[1]
            plt.scatter(pt_x, pt_y, c='black', marker='^', linewidths=4, alpha=1)
            plt.text(pt_x, pt_y - 7, bs.id,
                     horizontalalignment='center', verticalalignment='center',
                     fontsize=7, color='black')

        # 绘制列车
        for train in self.trains:  # 假设self.trains是列车列表
            pt_x = train.location[0]
            pt_y = train.location[1]
            plt.scatter(pt_x, pt_y, c='orange', marker='s', linewidths=1, alpha=1)
            plt.text(pt_x, pt_y + 2, train.id,
                     horizontalalignment='center', verticalalignment='center',
                     fontsize=7, color='orange')

        # 绘制无人机轨迹（确保 trajectory 是 NumPy 数组）
        for i in range(self.num_uavs):
            self.uavs[i].trajectory = np.array(self.uavs[i].trajectory)
            plt.plot(self.uavs[i].trajectory[:, 0], self.uavs[i].trajectory[:, 1], (0.1, i / self.num_uavs, 0.5),
                     label=f'UAV {i}')
            # 绘制每一次移动位置的点
            plt.scatter(self.uavs[i].trajectory[:, 0], self.uavs[i].trajectory[:, 1], s=20,
                        color=(0.1, i / self.num_uavs, 0.5), alpha=0.6)
        # 设置坐标轴范围和图形尺寸
        plt.xlim(self.MIN_X, self.MAX_X)
        plt.ylim(self.MIN_Y, self.MAX_Y)
        plt.gcf().set_size_inches(16, 9.12)
        plt.tight_layout()
        plt.savefig(f'{save_dir}/fig_{iter}_{datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S")}.png', dpi=50)
        plt.clf()

        """Renders the environment.

        The set of supported modes varies per environment. (And some
        third-party environments may not support rendering at all.)
        By convention, if mode is:

        - human: render to the current display or terminal and
          return nothing. Usually for human consumption.
        - rgb_array: Return an numpy.ndarray with shape (x, y, 3),
          representing RGB values for an x-by-y pixel image, suitable
          for turning into a video.
        - ansi: Return a string (str) or StringIO.StringIO containing a
          terminal-style text representation. The text can include newlines
          and ANSI escape sequences (e.g. for colors).

        Note:
            Make sure that your class's metadata 'render.modes' key includes
              the list of supported modes. It's recommended to call super()
              in implementations to use the functionality of this method.

        Args:
            mode (str): the mode to render with

        Example:

        class MyEnv(Env):
            metadata = {'render.modes': ['human', 'rgb_array']}

            def render(self, mode='human'):
                if mode == 'rgb_array':
                    return np.array(...) # return RGB frame suitable for video
                elif mode == 'human':
                    ... # pop up a window and render
                else:
                    super(MyEnv, self).render(mode=mode) # just raise an exception
        """
